
namespace Behemoth.SharePoint.WebParts.ToolParts {

    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Text;

    using Microsoft.Office.Server;
    using Microsoft.Office.Server.UserProfiles;

    using Microsoft.SharePoint;
    using Microsoft.SharePoint.WebControls;
    using Microsoft.SharePoint.WebPartPages;

    using mosswebparts = Microsoft.SharePoint.WebPartPages;

    using Behemoth.SharePoint.WebParts;
    using Behemoth.SharePoint.Logging;

  public class ListItemPicker : ToolPart {

    private const string FRIENDLY_ERROR_MESSAGE = "An error has occurred. Please contact your administrator.";
    private const int MAX_ALLOWED_ITEMS = 2000;

    private ErrorBag _errors = new ErrorBag();
    private ULSTraceLogging _log = new ULSTraceLogging();

    private SPSite _currentSite = null;
    private SPWeb _currentWeb = null;
    private DropDownList _choicesDropDownList = new DropDownList();

    private object _webPartToEditPropertyValue = null;

    #region Public Tool Part Properties

    /// <summary>
    /// The name of the public property of WebPartToEdit that will be updated by this tool part.
    /// </summary>
    public string WebPartToEditPropertyName {
      get { return _webPartToEditPropertyName; }
      set { _webPartToEditPropertyName = value; }
    }
    private string _webPartToEditPropertyName = string.Empty;

    /// <summary>
    /// The url for the target web site that contains the list to be displayed by this web part.
    /// Empty string implies current web site, single slash for site collection root, or provide
    /// a root-site relative url.
    /// </summary>
    public string TargetWebUrl {
      get { return _targetWebUrl; }
      set { _targetWebUrl = value; }
    }
    private string _targetWebUrl = string.Empty;

    /// <summary>
    /// The name of the list that contains items to be displayed in the dropdown.
    /// </summary>
    private string _targetListName = string.Empty;
    public string TargetListName {
      get { return _targetListName; }
      set { _targetListName = value; }
    }

    /// <summary>
    /// The fieldName name for target list that will be used for the text in dropdown.
    /// Default is 'Title'.
    /// </summary>
    public string TargetListTextFieldName {
      get { return _targetListTextFieldName; }
      set { _targetListTextFieldName = value; }
    }
    private string _targetListTextFieldName = "Title";

    /// <summary>
    /// The fieldName name for target list that will be used for the text in dropdown.
    /// Default is 'ID'.
    /// </summary>
    public string TargetListValueFieldName {
      get { return _targetListValueFieldName; }
      set { _targetListValueFieldName = value; }
    }
    private string _targetListValueFieldName = "ID";

    /// <summary>
    /// If true, the dropdown will display a choice at the top allowing users to select None as an option.
    /// </summary>
    public bool AddNoParentChoice {
      get { return _addNoParentChoice; }
      set { _addNoParentChoice = value; }
    }
    private bool _addNoParentChoice = true;

    /// <summary>
    /// If enabled, the text that will appear for the 'None' choice at the top of the dropdown.
    /// </summary>
    public string NoneChoiceText {
      get { return _NoneChoiceText; }
      set { _NoneChoiceText = value; }
    }
    private string _NoneChoiceText = "None";

    /// <summary>
    /// If enabled, the value set for the 'None' choice at the top of the dropdown.
    /// </summary>
    public object NoneChoiceValue {
      get { return _NoneChoiceValue; }
      set { _NoneChoiceValue = value; }
    }
    private object _NoneChoiceValue = string.Empty;

    /// <summary>
    /// If you wish to filter the results shownn in the toolpart dropdown,
    /// you can provide a CAML query to determine what will be retreived from
    /// the target list.
    /// </summary>
    public SPQuery TargetListQuery {
      get { return _targetListQuery; }
      set { _targetListQuery = value; }
    }
    private SPQuery _targetListQuery = null;
      
    #endregion

    /// <summary>
    /// Initializes a new instance of the <see cref="ListItemPicker"/> class.
    /// </summary>
    public ListItemPicker() {
      this.Title = "Select List Item";
      _currentSite = SPControl.GetContextSite(this.Context);
      _currentWeb = SPControl.GetContextWeb(this.Context);
    }

    /// <summary>
    /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
    /// </summary>
    protected override void CreateChildControls() {
      if (!_errors.HasErrors) {
        base.CreateChildControls();

        try {
          SyncWebPartToEditPropertyValue();
        } catch (Exception ex) {
          HandleError(FRIENDLY_ERROR_MESSAGE, ex, true);
        }
        InitializeCategoriesDropDownList();
        Controls.Add(_choicesDropDownList);
      }
    }

    #region Working Logic for Apply and Sync Changes

    private void SyncWebPartToEditPropertyValue() {
      object o = WebPartUtilities.GetWebPartProperty(this.WebPartToEdit, this.WebPartToEditPropertyName);
      if (o != null)
        _webPartToEditPropertyValue = o;
    }
    private void ApplyWebPartToEditPropertyValue() {
      Type t = WebPartUtilities.GetWebPartPropertyType(this.WebPartToEdit, this.WebPartToEditPropertyName);
      object o = null;
      if (_choicesDropDownList.SelectedValue != null && !_choicesDropDownList.SelectedValue.Equals(this.NoneChoiceValue.ToString())) {
        bool success = Behemoth.Parser.TryParse(_choicesDropDownList.SelectedValue, t, ParseFlags.Simple | ParseFlags.Invoke, out o);
        if (!success)
          throw new Exception(string.Format("Failed to parse dropdown value '{0}' as web part property {1} of type {2}; possible type mismatch.", _choicesDropDownList.SelectedValue, this.WebPartToEditPropertyName, t.FullName.ToString()));
      }
      this._webPartToEditPropertyValue = o;
      WebPartUtilities.SetWebPartProperty(this.WebPartToEdit, this.WebPartToEditPropertyName, this._webPartToEditPropertyValue);
    }

    #endregion

    #region Populate Dropdown Lists

    /// <summary>
    /// Populates the roles drop down list.
    /// </summary>
    private void InitializeCategoriesDropDownList() {
      List<ListItem> choices = GetListItemsFromTargetList();
      foreach (ListItem choice in choices) {
        _choicesDropDownList.Items.Add(choice);
      }
    }

    private List<ListItem> GetListItemsFromTargetList() {
      if (this._currentSite == null)
        throw new ArgumentNullException("_currentSite", "You must set the current web context before calling this method.");
      List<ListItem> choices = null;
      switch (this.TargetWebUrl) {
        case "":
          choices = GetListItemsFromTargetList(this._currentWeb);
          break;
        case "/":
          choices = GetListItemsFromTargetList(this._currentSite.RootWeb);
          break;
        default:
          using (SPWeb targetWeb = this._currentSite.OpenWeb(this.TargetWebUrl)) {
            choices = GetListItemsFromTargetList(targetWeb);
          }
          break;
      }
      return choices;
    }
    private List<ListItem> GetListItemsFromTargetList(SPWeb targetWeb) {
      if (targetWeb == null)
        throw new ArgumentNullException("targetWeb", string.Format("Target web is null for TargetWebUrl='{0}'.", this.TargetWebUrl));
      SPList targetList = TryAndGetTargetList(targetWeb);
      if (targetList == null)
        throw new ArgumentNullException("targetList", string.Format("Could not reteive target list names {0} from web {1}.", this.TargetListName, targetWeb.Url));

      List<ListItem> choices = new List<ListItem>();
      if (this.AddNoParentChoice) {
        ListItem emptyCategoryListItem = new ListItem(this.NoneChoiceText, this.NoneChoiceValue.ToString());
        choices.Add(emptyCategoryListItem);
      }

      SPListItemCollection items = null;
      if (this.TargetListQuery == null) {
        if (targetList.ItemCount > MAX_ALLOWED_ITEMS)
          throw new Exception(string.Format("The target list contains more than {0} items. You should provide a CAML query for this toolpart to reduce the number of items retreived.", MAX_ALLOWED_ITEMS));
        items = targetList.Items;
      } else {
        items = targetList.GetItems(this.TargetListQuery);
      }
      foreach (SPListItem item in items) {
        AddListItemFromTargetList(choices, item);
      }

      return choices;
    }

    private void AddListItemFromTargetList(List<ListItem> choices, SPListItem currentItem) {
      if (currentItem == null)
        return;
        string currentItemTitle = GetListItemFieldValue(currentItem, this.TargetListTextFieldName);
        string currentItemId = GetListItemFieldValue(currentItem, this.TargetListValueFieldName);
        ListItem currentListItem = new ListItem(currentItemTitle, currentItemId);
        choices.Add(currentListItem);
    }

    private SPList TryAndGetTargetList(SPWeb targetWeb) {
      SPList targetList = null;
      try {
        targetList = targetWeb.Lists[this.TargetListName];
      } catch { }
      return targetList;
    }

    private string GetListItemFieldValue(SPListItem item, string fieldName) {
      string value = (item[fieldName] != null) ? item[fieldName].ToString() : string.Empty;
      return value;
    }

    #endregion

    /// <summary>
    /// Set the dropdown index based on the currently selected value of the web part property.
    /// </summary>
    private void SetCurrentListItemSelection() {
      EnsureChildControls();
      string compareValue = _webPartToEditPropertyValue == null ? string.Empty : _webPartToEditPropertyValue.ToString();
      foreach (ListItem choice in _choicesDropDownList.Items) {
        choice.Selected = (string.Compare(choice.Value, compareValue, true) == 0);
      }
    }



    public override void SyncChanges() {
      base.SyncChanges();
      try {
        SyncWebPartToEditPropertyValue();
      } catch (Exception ex) {
        HandleError(FRIENDLY_ERROR_MESSAGE, ex, true);
      }
    }

    /// <summary>
    /// Called when the user clicks the OK or the Apply button in the tool pane.
    /// </summary>
    public override void ApplyChanges() {
      base.ApplyChanges();
      try {
        ApplyWebPartToEditPropertyValue();
      } catch (Exception ex) {
        HandleError(FRIENDLY_ERROR_MESSAGE, ex, true);
      }
    }

    protected override void OnPreRender(EventArgs e) {
      try {
        this.SetCurrentListItemSelection();
      } catch (Exception ex) {
        HandleError(FRIENDLY_ERROR_MESSAGE, ex, true);
      }
      base.OnPreRender(e);
    }

    protected override void RenderToolPart(HtmlTextWriter output) {
      // output any errors, warnings, or info messages that may have been added.
      _errors.Render(output);
      // if there was an error don't attempt to render the toolpart itself
      if (_errors.HasErrors)
        return;
      else
        base.RenderToolPart(output);
    }

    private void HandleError(string msg, Exception ex, bool isError) {
      if (isError || ex != null) {
        _errors.AddError(msg);
        _errors.AddError(ex);
      } else {
        _errors.AddWarning(msg);
      }
      _log.Write(msg, isError ? TraceSeverity.Unexpected : TraceSeverity.WarningEvent);
      if (ex != null)
        _log.Write(ex);
    }

  } // class
} // namespace

